//------------------------------------------------------------------------------ 
//      Copyright (c) 2004-2008, Be Nanashi-San 
// 
//      Permission is hereby granted, free of charge, to any person obtaining a 
//      copy of this software and associated documentation files (the "Software"), 
//      to deal in the Software without restriction, including without limitation 
//      the rights to use, copy, modify, merge, publish, distribute, sublicense, 
//      and/or sell copies of the Software, and to permit persons to whom the 
//      Software is furnished to do so, subject to the following conditions: 
// 
//      The above copyright notice and this permission notice shall be included in 
//      all copies or substantial portions of the Software. 
// 
//      THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
//      IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
//      FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
//      AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
//      LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
//      FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
//      DEALINGS IN THE SOFTWARE. 
// 
//      File Name:	ThreadWriteWindow.cpp
//      Author: 	Be Nanashi-San 
//      Description:	2ch Thread Writing GUI.
//------------------------------------------------------------------------------

// TODO: 2ch以外へのカキコ。
// TODO: HTMLフォームからのクッキー取得。
// TODO: 書き込み判定。（タイトルだけでいいいのか？）
// TODO: スレ一覧件数アップデート。
// TODO: 投稿確認文字列の取得。（プログラムに埋め込まない。）
#include "config.h"

#include <Alert.h>
#include <Button.h>
#include <Debug.h>

#include <ScrollView.h>
#include <TextControl.h>
#include <TextView.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "2chPositive.h"
#include "NetUtil.h"
#include "Util.h"
#include "ThreadWriteWindow.h"
#include "messages.h"
#include "setting.h"

#define BRECT(x,y,w,h) BRect((x), (y), (x+w-1), (y+h-1))

static const unsigned int kOKBUTTON = 'kOKB';
static const unsigned int kCANSELBUTTON = 'kCLB';
static const char * const kSETTING_THREADWRITEWINDOW = "ThreadWriteWindow";


////////////////////////////////////////////////////////////////////////////////
// ThreadWriteWindow
//
//
////////////////////////////////////////////////////////////////////////////////
ThreadWriteWindow::ThreadWriteWindow()
: BWindow(BRECT(200,100,400,200), "WriteThread", B_TITLED_WINDOW, B_NOT_ZOOMABLE | B_NOT_MINIMIZABLE | B_ASYNCHRONOUS_CONTROLS)
,fName(NULL)
,fMailto(NULL)
,fContent(NULL)
{
	this->SetSizeLimits(400, 800, 200, 700);
	BView *contentView = new BView(this->Bounds(), "ContentView", B_FOLLOW_ALL, 0);
	contentView->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
	this->AddChild(contentView);
	//名前
	fName = new BTextControl(BRECT(6, 7, 320, 20), "name", "名　前：", NULL, NULL);
	fName->SetDivider(60);
	contentView->AddChild(fName);
	//メール
	fMailto = new BTextControl(BRECT(6, 30, 320, 20), "mail", "メール：", NULL, NULL);
	fMailto->SetDivider(60);
	contentView->AddChild(fMailto);
	//本文
	// Be sample code(intro/TextEditor)よりB_PULSE_NEEDEDを追加。（いるのか？）
	fContent = new BTextView(BRECT(6, 55, 375, 100), "text", this->Bounds(), B_FOLLOW_ALL, B_FRAME_EVENTS | B_WILL_DRAW | B_NAVIGABLE );
	//BeOS - Zeta R4スレの218氏より、横スクロールバー削除。
	BScrollView *aScrollView = new BScrollView("", fContent, B_FOLLOW_ALL, 0, false, true);
	contentView->AddChild(aScrollView);
	fContent->SetTextRect(fContent->Bounds());
	//カキコ
	BButton* aButton = new BButton(BRECT(300, 172, 75, 20), "", "書き込む", new BMessage(kOKBUTTON), B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM);
	contentView->AddChild(aButton);
	//取り消し
	aButton = new BButton(BRECT(225, 172, 75, 20), "", "取り消し", new BMessage(kCANSELBUTTON), B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM);
	contentView->AddChild(aButton);

	fName->SetText("Be名無しさん");
	fMailto->SetText("sage");
	
	BPoint point;
 	status_t sts = theSettings().GetPoint(kSETTING_THREADWRITEWINDOW, &point);
	if(sts == B_OK) this->MoveTo(point);

	//20050717:BeOS - Zeta R4スレ@385：Be名無しさんより
	fCookie = "";

}
////////////////////////////////////////////////////////////////////////////////
// QuitRequested
// 終了時にウインドウ位置を記録する。
//
////////////////////////////////////////////////////////////////////////////////
bool
ThreadWriteWindow::QuitRequested()
{
	theSettings().SetPoint(kSETTING_THREADWRITEWINDOW, this->Frame().LeftTop());
	be_app_messenger.SendMessage(kMSG_CLOSE_WRITE_WINDOW);

	return true;
}
////////////////////////////////////////////////////////////////////////////////
// DispatchMessage
//
// Zeta R4すれ396のBe名無しさんより。TAB移動用。
////////////////////////////////////////////////////////////////////////////////
void 
ThreadWriteWindow::DispatchMessage(BMessage *msg, BHandler *handler) 
{ 
	if ((msg->what == B_KEY_DOWN) && (msg->FindInt8("byte") == B_TAB) && (strcmp(handler->Name(), "text") == 0)) { 
		ChildAt(0)->ChildAt(3)->MakeFocus(true); 
	}else{
		BWindow::DispatchMessage(msg, handler);
	}
} 
////////////////////////////////////////////////////////////////////////////////
// MessageReceived
//
//
////////////////////////////////////////////////////////////////////////////////
void
ThreadWriteWindow::MessageReceived(BMessage *msg)
{
	switch (msg->what){
		case kOKBUTTON:
			if (this->sendData()){ 
				fContent->SetText(""); 
			}
			this->Hide(); 
		break;

		case kCANSELBUTTON:
			this->Hide();
			break;

		case kMEG_WRITE_BUTTON:{ //StatusBar→DisplayThreadViewより
			BMessage m;
			BString title;
			msg->FindMessage("sure", &m);
			m.FindString("title", &title);
			this->SetTitle(title.String());
			m.FindString("dat", &fCurrentDat);
			
			if(fCurrentHost != m.FindString("host")){
				m.FindString("host", &fCurrentHost);
				//20060730:板が違う時はクッキーをクリアー
				fCookie = "";
			}
			break;
		}
		
		default:
			BWindow::MessageReceived (msg);
			break;
	}
}
////////////////////////////////////////////////////////////////////////////////
// FrameResized
// BeOS - Zeta R4スレの212氏より。
// 本文のBTextViewのリサイズ用。
////////////////////////////////////////////////////////////////////////////////
void
ThreadWriteWindow::FrameResized(float /*width*/, float /*height*/)
{
	fContent->SetTextRect(fContent->Bounds());
}
////////////////////////////////////////////////////////////////////////////////
// checkResponseHeader
// 返答ヘッダーの確認とクッキー取得。
// 20060726:メンバー化。
////////////////////////////////////////////////////////////////////////////////
status_t 
ThreadWriteWindow::checkResponseHeader(BMallocIO &header)
{
	const char *s = static_cast <const char *>(header.Buffer());
	char tmpcookie[256];
	tmpcookie[0] = 0;
	char *pcookie = tmpcookie;

	for(;;){
		//ヘッダー終了チェック
		if (*s =='\r' && *(s + 1) == '\n' && *(s + 2)=='\r' && *(s + 3) == '\n' ){
			s += 4;
			break;
		}
		
		//改行スキップ
		if (*s =='\r' && *(s + 1) == '\n' ){
			s += 2;
		}
		
		//ステータスコードチェック
		if (*s =='H' && strncmp("HTTP/", s, 5)==0){
			s += 9;
			if(strncmp(s, "200", 3)) return B_ERROR; //20050625:atoi()から変更。
			s += 3;
		}

		// クッキー取得
		// 2005-06-07：複数行に渡るクッキー（HAP）に対応
		// 20060730:fCookieが空の時のみ対応
		if (!fCookie.Length() && *s =='S' && strncmp("Set-Cookie:", s, 11)==0){
			s += 12;
			while(*s != ';') *pcookie++ = *s++;
			//20060726
			*pcookie++ = ';';
			*pcookie++ = ' ';
		}
		
		while(*s != '\r') s++;
	}

	if(!fCookie.Length() && tmpcookie[0]){
		// 20060726:fCookieに直接代入。
		// 20060730:fCookieが空の時のみ対応
		*pcookie = 0; //20060727:BeOS - Zeta R5すれ@347 ：Be名無しさん より 
		fCookie = tmpcookie;
	}
	
	return B_OK;
}

////////////////////////////////////////////////////////////////////////////////
// GetErrorString
// 20060731
// 20070621 : アクセス規制Error対応
////////////////////////////////////////////////////////////////////////////////
static void
GetErrorString(BString &error, const char *start, const char *end)
{
	const char *ss = start;
	const char *s;
	error =  "";
	for(s = ss; s < end; s++){ 
		if( *s != '<') continue;
		
		if(strncmp(s, "<b>", 3) == 0 ){ 
			s += 3; 
			while( s < end && *s != '<' && *(s + 1) != '/') error << *s++; 
			break; 
		}else if (strncmp(s, "<!-- 2ch_X:error -->", 20) == 0 ){ 
			s += 20; 
			while(strncmp(s, "<b>", 3) != 0) s++;
			s += 3;
			while( *s != '<' ) error << *s++; 
			break; 
		} 
	}
}
////////////////////////////////////////////////////////////////////////////////
// checktitle
//
// 参考：と～く２ちゃんねる（http://age.s22.xrea.com/talk2ch/）
////////////////////////////////////////////////////////////////////////////////
write_status
ThreadWriteWindow::checkTitle(BMallocIO &nbuf, BString &title)
{
BMallocIO utf8;
write_status sts;

	title = "";
	nbuf.Seek(0, SEEK_SET);
	cutf8(&nbuf, &utf8);
	const char *s = static_cast <const char *>(utf8.Buffer());
	const char *end = s + utf8.BufferLength();

	for(; s < end; s++){
		if( (*s == '<') && strncmp(s, "<title>", 7) == 0 ){
			s += 7;
			while( *s != '<' && *(s + 1) != '/') title << *s++;
			break;
		}
	}
	
	if(title.FindFirst("書きこみました") >= 0){	//書き込みが成功
		sts= WRITE_OK;
	}else if(title.FindFirst("お茶でも") >= 0){	//サーバの負荷が高く、書き込めない場合
		sts = WRITE_WAIT;
	}else if(title.FindFirst("書き込み確認") >= 0){	//クッキー確認の場合
		sts = WRITE_COOKIE_CHECK; 
	}else if(title.FindFirst("ＥＲＲＯＲ") >= 0 ){	//書き込みが失敗する
		sts = WRITE_ERROR;
		//20060728:BeOS - Zeta R4スレ@331氏より、エラーの詳細内容を表示。
		GetErrorString(title, s, end);
	}else{
		sts = WRITE_UNKNOWN;
	}
		
	return sts;
}


////////////////////////////////////////////////////////////////////////////////
// AlertStatus
//
//20060726:書き込み判定のタイトル化。
////////////////////////////////////////////////////////////////////////////////
/**********************************************************************************
20060528:
TODO:
 1.13では強制的にhama=mogeraつけてるだけなので、フォームの内容ちゃんと読んで 
 bbs、key、time、FROM、mail、MASSAGE以外のinput type=hiddenのデータがあったらそれを付加して書き込むようにしてみました。 
 複数あっても大丈夫です。 
 1.13  
 　ハナモゲラ付き書き込み→クッキー確認→クッキーを食べる→ハナモゲラとクッキーつけて書き込み  
 1.14b1  
 　ハナモゲラ無し書き込み→クッキー確認→クッキーを食べて、フォームのイレギュラーな項目を抽出→クッキーと抽出したフォームをつけて書き込み  
 となってます。 
 フォームの内容調べるのは、クッキー確認画面（2ch_X:cookie）の時になってます。 
 こんな感じで理想的ですかね？ 

***************************************************************************************/
void
ThreadWriteWindow::AlertStatus(write_status &sts, BString &title)
{
	const char *kakunin = 
	{ "投稿確認\n"
	"・投稿者は、投稿に関して発生する責任が全て投稿者に帰すことを承諾します。\n"
	"・投稿者は、話題と無関係な広告の投稿に関して、相応の費用を支払うことを承諾します\n"
	"・投稿者は、投稿された内容及びこれに含まれる知的財産権、\n"
	"　（著作権法第21条ないし第28条に規定される権利も含む）その他の権利につき\n"
	"　（第三者に対して再許諾する権利を含みます。）、\n"
	"　掲示板運営者に対し、無償で譲渡することを承諾します。\n"
	"　ただし、投稿が別に定める削除ガイドラインに該当する場合、\n"
	"　投稿に関する知的財産権その他の権利、義務は一定期間投稿者に留保されます。\n"
	"・掲示板運営者は、投稿者に対して日本国内外において\n"
	"　無償で非独占的に複製、公衆送信、頒布及び翻訳する権利を投稿者に許諾します。\n"
	"　また、投稿者は掲示板運営者が指定する第三者に対して、\n"
	"　一切の権利（第三者に対して再許諾する権利を含みます）を許諾しないことを承諾します。\n"
	"・投稿者は、掲示板運営者あるいはその指定する者に対して、著作者人格権を一切行使しないことを承諾します。"
	};
	

	//TODO:キャンセル処理
	if(sts == WRITE_COOKIE_CHECK){
		int32 btn = (new BAlert("", kakunin, "キャンセル", "OK"))->Go();
		if (btn == 0) sts = WRITE_CANCEL;
	}else {
#ifdef	NO_ALERT_WRITE_OK
		if (sts != WRITE_OK)
#endif
		(new BAlert("", title.String(), "OK"))->Go();
	}
}

////////////////////////////////////////////////////////////////////////////////
// makeHeader
// 参考：と～く２ちゃんねる（http://age.s22.xrea.com/talk2ch/）
////////////////////////////////////////////////////////////////////////////////
//TODO: クッキーはエンコードするべきか？？
void
ThreadWriteWindow::makeHeader(BString &header)
{
BString content;
char hostname[32];
char keyname[32];
char bbsname[32];

BString sendname, sendmailto, sendcontent;
const char * const submit="%8f%91%82%ab%8d%9e%82%de";	//“書き込む”（エンコード済み）

	Urlescape(fName->Text(), sendname);
	Urlescape(fMailto->Text(), sendmailto);
	Urlescape(fContent->Text(), sendcontent);

	uri2host(fCurrentHost.String(), hostname, bbsname);
	strcpy(keyname, fCurrentDat.String());
	strtok(keyname, ".");

	content = "";
	content
		<< "bbs=" << bbsname
		<< "&key=" << keyname
		<< "&time=" << time(NULL) - 100
		<< "&submit=" << submit
		<< "&FROM="<< sendname
		<< "&mail=" << sendmailto
		<< "&MESSAGE=" << sendcontent;
	
	header = "";
	header
		<< "POST /test/bbs.cgi HTTP/1.0\r\n"
		<< "Host:" << hostname << "\r\n"
		<< "Content-length:" << content.Length() << "\r\n"
		<< "Referer: http://"<< hostname << "/" << bbsname << "/index2.html\r\n"
		<< "Content-Type: application/x-www-form-urlencoded\r\n"
		<< "User-Agent: Monazilla/1.00 2ch+/" VERSION_2CHPLUS "\r\n"
		<< "Cookie: "<< fCookie //<< "; "20060726
		<< "NAME=" << sendname << "; "
		<< "MAIL=" << sendmailto << "; tepo=don\r\n"//20081016:2chクッキー仕様変更に対応
		<< "Connection: close\r\n"
		<< "\r\n"
		<< content
		<< "\r\n";
}

////////////////////////////////////////////////////////////////////////////////
// sendData
// bbs.cgiを使ってレスを書き込む。
//
// 参考：と～く２ちゃんねる（http://age.s22.xrea.com/talk2ch/）
////////////////////////////////////////////////////////////////////////////////
bool
ThreadWriteWindow::sendData()
{
	BString requestheader;
	char hostname[32];
	write_status sts = WRITE_ERROR;
	BString title;
	BMallocIO nbuf;

	uri2host(fCurrentHost.String(), hostname);

	for(int32 i = 0; i < 2; i++){ // stopper
		this->makeHeader(requestheader);
		if( getData(requestheader.String(), hostname, nbuf) != B_OK || this->checkResponseHeader(nbuf) != B_OK){
			sts = COMM_ERR;
			break;
		}
			
		sts = this->checkTitle(nbuf, title);
		this->AlertStatus(sts, title);
		
		if (sts != WRITE_COOKIE_CHECK) break;
	}

	if (sts == COMM_ERR) (new BAlert("","通信エラーです。","OK"))->Go();	

#ifdef ALWAYS_LAST_SHOW
	//20050717:BeOS - Zeta R4スレの205氏よりスレ一覧リフレッシュ
	// リフレッシュによって表示位置がかわってしまうので最後を重視して見ている人以外は自動で
	// リフレッシュしない方がいいだろう
	if (sts == WRITE_OK){
		BMessage message(kMENU_THREAD_REFREASH);
		be_app_messenger.SendMessage(&message);
	}	
#endif
	return (sts == WRITE_OK);
}


